@file:OptIn(ExperimentalForeignApi::class)

package com.tencent.compose.utils

import androidx.compose.ui.napi.JsEnv
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.alloc
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.value
import platform.ohos.napi_create_array
import platform.ohos.napi_value
import platform.ohos.napi_valueVar

data class FooObject(
    val id: String,
    var str1Value: String = "",
    var str2Value: String = "",
    var str3Value: String = generateRandomString(10),
    var str4Value: String = generateRandomString(100),
    var str5Value: String = generateRandomString(1000),
    var str6Value: String = generateRandomString(10000),
    var str7Value: String = "",
    var str8Value: String = "",
    var str9Value: String = "",
    var str10Value: String = "",
    var boolValue: Boolean = false,
    var intValue: Int = 0,
    var longValue: Long = 0,
    var doubleValue: Double = 0.0,
    var fooValue: FooObject? = null,  // 使用可空类型避免无限递归
    var listValue: List<FooObject?>? = null,
    var mapValue: Map<String, FooObject?>? = null,
    var mapListValue: Map<String, List<FooObject?>?>? = null
)

// JsEnv ------

fun JsEnv.setNamedProperty(obj: napi_value?, key: String, value: napi_value?) {
    JsEnv.setProperty(obj, JsEnv.createStringUtf8(key), value)
}

fun JsEnv.createArray(): napi_value? {
    return memScoped {
        val result = alloc<napi_valueVar>()
        napi_create_array(env(), result.ptr)
        result.value
    }
}

fun JsEnv.getArray(obj: napi_value?): List<napi_value?> {
    val length = JsEnv.getArrayLength(obj)
    return List(length) { JsEnv.getElement(obj, it) }
}

fun JsEnv.getNamedList(obj: napi_value?, key: String): List<napi_value?> {
    return JsEnv.getArray(JsEnv.getNamedProperty(obj, key))
}

fun JsEnv.setNamedList(obj: napi_value?, key: String, list: List<napi_value?>?) {
    if (list == null) {
        JsEnv.setNamedProperty(obj, key, null)
        return
    }
    val array = JsEnv.createArray()
    list.forEachIndexed { index, value -> JsEnv.setElement(array, index, value) }
    JsEnv.setNamedProperty(obj, key, array)
}

fun JsEnv.getMap(obj: napi_value?): Map<String, napi_value?> {
    val propertyNames = JsEnv.getAllPropertyNames(obj)
    val length = JsEnv.getArrayLength(propertyNames)
    val names = List(length) {
        JsEnv.getValueStringUtf8(JsEnv.getElement(propertyNames, it))
    }.filterNotNull()
    return names.associateWith { JsEnv.getNamedProperty(obj, it) }
}

fun JsEnv.getNamedMap(obj: napi_value?, key: String): Map<String, napi_value?> {
    return getMap(JsEnv.getNamedProperty(obj, key))
}

fun JsEnv.setNamedMap(obj: napi_value?, key: String, fooMap: Map<String, napi_value?>?) {
    if (fooMap == null) {
        JsEnv.setNamedProperty(obj, key, null)
        return
    }
    val mapValue = JsEnv.createObject()
    fooMap.forEach { entry ->
        JsEnv.setNamedProperty(mapValue, entry.key, entry.value)
    }
    JsEnv.setNamedProperty(obj, key, mapValue)
}

object JsFoo {
    fun toNapiValue(foo: FooObject?): napi_value? {
        if (foo == null) return null
        val obj = JsEnv.createObject() ?: return null
        JsEnv.setNamedProperty(obj, "id", JsEnv.createStringUtf8(foo.id))
        JsEnv.setNamedProperty(obj, "str1Value", JsEnv.createStringUtf8(foo.str1Value))
        JsEnv.setNamedProperty(obj, "str2Value", JsEnv.createStringUtf8(foo.str2Value))
        JsEnv.setNamedProperty(obj, "str3Value", JsEnv.createStringUtf8(foo.str3Value))
        JsEnv.setNamedProperty(obj, "str4Value", JsEnv.createStringUtf8(foo.str4Value))
        JsEnv.setNamedProperty(obj, "str5Value", JsEnv.createStringUtf8(foo.str5Value))
        JsEnv.setNamedProperty(obj, "str6Value", JsEnv.createStringUtf8(foo.str6Value))
        JsEnv.setNamedProperty(obj, "str7Value", JsEnv.createStringUtf8(foo.str7Value))
        JsEnv.setNamedProperty(obj, "str8Value", JsEnv.createStringUtf8(foo.str8Value))
        JsEnv.setNamedProperty(obj, "str9Value", JsEnv.createStringUtf8(foo.str9Value))
        JsEnv.setNamedProperty(obj, "str10Value", JsEnv.createStringUtf8(foo.str10Value))
        JsEnv.setNamedProperty(obj, "boolValue", JsEnv.getBoolean(foo.boolValue))
        JsEnv.setNamedProperty(obj, "intValue", JsEnv.createInt32(foo.intValue))
        JsEnv.setNamedProperty(obj, "longValue", JsEnv.createInt64(foo.longValue))
        JsEnv.setNamedProperty(obj, "doubleValue", JsEnv.createDouble(foo.doubleValue))
        JsEnv.setNamedProperty(obj, "fooValue", toNapiValue(foo.fooValue))

        val listValue = foo.listValue?.map { toNapiValue(it) }
        JsEnv.setNamedList(obj, "listValue", listValue)
        val mapValue = foo.mapValue?.map { (key, value) ->
            key to toNapiValue(value)
        }?.toMap()
        JsEnv.setNamedMap(obj, "mapValue", mapValue)
        val mapListValue = foo.mapListValue?.map { (key, value) ->
            key to toNapiValue(value)
        }?.toMap()
        JsEnv.setNamedMap(obj, "mapListValue", mapListValue)
        return obj
    }

    private fun toNapiValue(list: List<FooObject?>?): napi_value? {
        val obj = JsEnv.createArray()
        list?.forEachIndexed { index, value ->
            JsEnv.setElement(
                obj,
                index,
                toNapiValue(value)
            )
        }
        return obj
    }

    fun toFooObject(value: napi_value?): FooObject? {
        if (value == null) return null
        val id =
            JsEnv.getNamedProperty(value, "id")?.let { JsEnv.getValueStringUtf8(it) } ?: return null
        val str1Value =
            JsEnv.getNamedProperty(value, "str1Value")?.let { JsEnv.getValueStringUtf8(it) } ?: ""
        val str2Value =
            JsEnv.getNamedProperty(value, "str2Value")?.let { JsEnv.getValueStringUtf8(it) } ?: ""
        val str3Value =
            JsEnv.getNamedProperty(value, "str3Value")?.let { JsEnv.getValueStringUtf8(it) } ?: ""
        val str4Value =
            JsEnv.getNamedProperty(value, "str4Value")?.let { JsEnv.getValueStringUtf8(it) } ?: ""
        val str5Value =
            JsEnv.getNamedProperty(value, "str5Value")?.let { JsEnv.getValueStringUtf8(it) } ?: ""
        val str6Value =
            JsEnv.getNamedProperty(value, "str6Value")?.let { JsEnv.getValueStringUtf8(it) } ?: ""
        val str7Value =
            JsEnv.getNamedProperty(value, "str7Value")?.let { JsEnv.getValueStringUtf8(it) } ?: ""
        val str8Value =
            JsEnv.getNamedProperty(value, "str8Value")?.let { JsEnv.getValueStringUtf8(it) } ?: ""
        val str9Value =
            JsEnv.getNamedProperty(value, "str9Value")?.let { JsEnv.getValueStringUtf8(it) } ?: ""
        val str10Value =
            JsEnv.getNamedProperty(value, "str10Value")?.let { JsEnv.getValueStringUtf8(it) } ?: ""
        val boolValue =
            JsEnv.getNamedProperty(value, "boolValue")?.let { JsEnv.getValueBool(it) } ?: false
        val intValue =
            JsEnv.getNamedProperty(value, "intValue")?.let { JsEnv.getValueInt32(it) } ?: 0
        val longValue =
            JsEnv.getNamedProperty(value, "longValue")?.let { JsEnv.getValueInt64(it) } ?: 0L
        val doubleValue =
            JsEnv.getNamedProperty(value, "doubleValue")?.let { JsEnv.getValueDouble(it) } ?: 0.0
        val fooValue = JsEnv.getNamedProperty(value, "fooValue")?.let { toFooObject(it) }
        val listValue = JsEnv.getNamedList(value, "listValue").let {
            it.map { value ->
                toFooObject(value)
            }
        }
        val mapValue = JsEnv.getNamedMap(value, "mapValue")
            .map { (entryKey, entryValue) -> entryKey to toFooObject(entryValue) }
            .toMap()
        val mapListValue = JsEnv.getNamedMap(value, "mapListValue").map { (entryKey, entryValue) ->
            entryKey to JsEnv.getArray(entryValue).map { itemValue -> toFooObject(itemValue) }
        }.toMap()

        return FooObject(
            id,
            str1Value,
            str2Value,
            str3Value,
            str4Value,
            str5Value,
            str6Value,
            str7Value,
            str8Value,
            str9Value,
            str10Value,
            boolValue,
            intValue,
            longValue,
            doubleValue,
            fooValue,
            listValue,
            mapValue,
            mapListValue,
        )
    }
}

// TsEnv ------
fun TsEnv.setNamedProperty(obj: napi_value?, key: String, value: napi_value?) {
    TsEnv.setProperty(obj, TsEnv.createStringUtf8(key), value)
}

fun TsEnv.createArray(): napi_value? {
    return memScoped {
        val result = alloc<napi_valueVar>()
        napi_create_array(env(), result.ptr)
        result.value
    }
}

fun TsEnv.getArray(obj: napi_value?): List<napi_value?> {
    val length = TsEnv.getArrayLength(obj)
    return List(length) { TsEnv.getElement(obj, it) }
}

fun TsEnv.getNamedList(obj: napi_value?, key: String): List<napi_value?> {
    return TsEnv.getArray(TsEnv.getNamedProperty(obj, key))
}

fun TsEnv.setNamedList(obj: napi_value?, key: String, list: List<napi_value?>?) {
    if (list == null) {
        TsEnv.setNamedProperty(obj, key, null)
        return
    }
    val array = TsEnv.createArray()
    list.forEachIndexed { index, value -> TsEnv.setElement(array, index, value) }
    TsEnv.setNamedProperty(obj, key, array)
}

fun TsEnv.getMap(obj: napi_value?): Map<String, napi_value?> {
    val propertyNames = TsEnv.getAllPropertyNames(obj)
    val length = TsEnv.getArrayLength(propertyNames)
    val names = List(length) {
        TsEnv.getValueStringUtf8(TsEnv.getElement(propertyNames, it))
    }.filterNotNull()
    return names.associateWith { TsEnv.getNamedProperty(obj, it) }
}

fun TsEnv.getNamedMap(obj: napi_value?, key: String): Map<String, napi_value?> {
    return getMap(TsEnv.getNamedProperty(obj, key))
}

fun TsEnv.setNamedMap(obj: napi_value?, key: String, fooMap: Map<String, napi_value?>?) {
    if (fooMap == null) {
        TsEnv.setNamedProperty(obj, key, null)
        return
    }
    val mapValue = TsEnv.createObject()
    fooMap.forEach { entry ->
        TsEnv.setNamedProperty(mapValue, entry.key, entry.value)
    }
    TsEnv.setNamedProperty(obj, key, mapValue)
}

object TsFoo {
    fun toNapiValue(foo: FooObject?): napi_value? {
        if (foo == null) return null
        val obj = TsEnv.createObject() ?: return null
        TsEnv.setNamedProperty(obj, "id", TsEnv.createStringUtf8(foo.id))
        TsEnv.setNamedProperty(obj, "str1Value", TsEnv.createStringUtf8(foo.str1Value))
        TsEnv.setNamedProperty(obj, "str2Value", TsEnv.createStringUtf8(foo.str2Value))
        TsEnv.setNamedProperty(obj, "str3Value", TsEnv.createStringUtf8(foo.str3Value))
        TsEnv.setNamedProperty(obj, "str4Value", TsEnv.createStringUtf8(foo.str4Value))
        TsEnv.setNamedProperty(obj, "str5Value", TsEnv.createStringUtf8(foo.str5Value))
        TsEnv.setNamedProperty(obj, "str6Value", TsEnv.createStringUtf8(foo.str6Value))
        TsEnv.setNamedProperty(obj, "str7Value", TsEnv.createStringUtf8(foo.str7Value))
        TsEnv.setNamedProperty(obj, "str8Value", TsEnv.createStringUtf8(foo.str8Value))
        TsEnv.setNamedProperty(obj, "str9Value", TsEnv.createStringUtf8(foo.str9Value))
        TsEnv.setNamedProperty(obj, "str10Value", TsEnv.createStringUtf8(foo.str10Value))
        TsEnv.setNamedProperty(obj, "boolValue", TsEnv.getBoolean(foo.boolValue))
        TsEnv.setNamedProperty(obj, "intValue", TsEnv.createInt32(foo.intValue))
        TsEnv.setNamedProperty(obj, "longValue", TsEnv.createInt64(foo.longValue))
        TsEnv.setNamedProperty(obj, "doubleValue", TsEnv.createDouble(foo.doubleValue))
        TsEnv.setNamedProperty(obj, "fooValue", toNapiValue(foo.fooValue))

        val listValue = foo.listValue?.map { toNapiValue(it) }
        TsEnv.setNamedList(obj, "listValue", listValue)
        val mapValue = foo.mapValue?.map { (key, value) ->
            key to toNapiValue(value)
        }?.toMap()
        TsEnv.setNamedMap(obj, "mapValue", mapValue)
        val mapListValue = foo.mapListValue?.map { (key, value) ->
            key to toNapiValue(value)
        }?.toMap()
        TsEnv.setNamedMap(obj, "mapListValue", mapListValue)
        return obj
    }

    private fun toNapiValue(list: List<FooObject?>?): napi_value? {
        val obj = TsEnv.createArray()
        list?.forEachIndexed { index, value ->
            TsEnv.setElement(
                obj,
                index,
                toNapiValue(value)
            )
        }
        return obj
    }

    fun toFooObject(value: napi_value?): FooObject? {
        if (value == null) return null
        val id =
            TsEnv.getNamedProperty(value, "id")?.let { TsEnv.getValueStringUtf8(it) } ?: return null
        val str1Value =
            TsEnv.getNamedProperty(value, "str1Value")?.let { TsEnv.getValueStringUtf8(it) } ?: ""
        val str2Value =
            TsEnv.getNamedProperty(value, "str2Value")?.let { TsEnv.getValueStringUtf8(it) } ?: ""
        val str3Value =
            TsEnv.getNamedProperty(value, "str3Value")?.let { TsEnv.getValueStringUtf8(it) } ?: ""
        val str4Value =
            TsEnv.getNamedProperty(value, "str4Value")?.let { TsEnv.getValueStringUtf8(it) } ?: ""
        val str5Value =
            TsEnv.getNamedProperty(value, "str5Value")?.let { TsEnv.getValueStringUtf8(it) } ?: ""
        val str6Value =
            TsEnv.getNamedProperty(value, "str6Value")?.let { TsEnv.getValueStringUtf8(it) } ?: ""
        val str7Value =
            TsEnv.getNamedProperty(value, "str7Value")?.let { TsEnv.getValueStringUtf8(it) } ?: ""
        val str8Value =
            TsEnv.getNamedProperty(value, "str8Value")?.let { TsEnv.getValueStringUtf8(it) } ?: ""
        val str9Value =
            TsEnv.getNamedProperty(value, "str9Value")?.let { TsEnv.getValueStringUtf8(it) } ?: ""
        val str10Value =
            TsEnv.getNamedProperty(value, "str10Value")?.let { TsEnv.getValueStringUtf8(it) } ?: ""
        val boolValue =
            TsEnv.getNamedProperty(value, "boolValue")?.let { TsEnv.getValueBool(it) } ?: false
        val intValue =
            TsEnv.getNamedProperty(value, "intValue")?.let { TsEnv.getValueInt32(it) } ?: 0
        val longValue =
            TsEnv.getNamedProperty(value, "longValue")?.let { TsEnv.getValueInt64(it) } ?: 0L
        val doubleValue =
            TsEnv.getNamedProperty(value, "doubleValue")?.let { TsEnv.getValueDouble(it) } ?: 0.0
        val fooValue = TsEnv.getNamedProperty(value, "fooValue")?.let { toFooObject(it) }
        val listValue = TsEnv.getNamedList(value, "listValue").let {
            it.map { value ->
                toFooObject(value)
            }
        }
        val mapValue = TsEnv.getNamedMap(value, "mapValue")
            .map { (entryKey, entryValue) -> entryKey to toFooObject(entryValue) }
            .toMap()
        val mapListValue = TsEnv.getNamedMap(value, "mapListValue").map { (entryKey, entryValue) ->
            entryKey to TsEnv.getArray(entryValue).map { itemValue -> toFooObject(itemValue) }
        }.toMap()

        return FooObject(
            id,
            str1Value,
            str2Value,
            str3Value,
            str4Value,
            str5Value,
            str6Value,
            str7Value,
            str8Value,
            str9Value,
            str10Value,
            boolValue,
            intValue,
            longValue,
            doubleValue,
            fooValue,
            listValue,
            mapValue,
            mapListValue,
        )
    }
}